#include "toolkit.h"
#include "setting.h"
#include "transferskt.h"
#include <QVariant>

#define PROP_CONN  "CONN"

#define MAXBUFFER (1024LL*1024LL)

TransferSkt::TransferSkt(QObject *parent)
: QObject(parent), m_spt(0), m_dpt(0)
{
}

TransferSkt::~TransferSkt()
{
}

bool TransferSkt::start(const QHostAddress& sip, quint16 spt, const QHostAddress& dip, quint16 dpt)
{
	m_sip = sip;
	m_dip = dip;
	m_spt = spt;
	m_dpt = dpt;

	m_conns.clear();
	m_error.clear();

	bool res = open();

	QString msg("start %1 transfer server %2!");
	if (!res)
	{
		msg = msg.arg(name(),"failed");
		if (!m_error.isEmpty())
		{	msg += " error:[";
			msg += m_error;
			msg += "].";
		}
	}
	else
	{
		msg = msg.arg(name(),"successfully");
	}

	show(msg);

	return res;
}

void TransferSkt::kill(const QString& key)
{
	void* cookie = getCookie(key);
	if (cookie)
	{
		if (close(cookie))
			emit connClose(key);
	}
}

void TransferSkt::stop()
{
	OBJMAP::const_iterator i;
	for (i = m_conns.constBegin(); i != m_conns.constEnd(); ++i)
	{
		QString k = i.key();
		void* v = i.value();

		if (close(v))
			emit connClose(k);
	}

	m_conns.clear();
	
	show(QString("stop %1 transfer server!").arg(name()));
}

void TransferSkt::recordRecv(qint32 bytes)
{
	emit countRecv(bytes);
}

void TransferSkt::recordSend(qint32 bytes)
{
	emit countSend(bytes);
}

void TransferSkt::getKeys(QStringList& res)
{
	res.clear();
	OBJMAP::const_iterator i;
	for (i = m_conns.constBegin(); i != m_conns.constEnd(); ++i)
		res << i.key();
}

void TransferSkt::setCookie(const QString& k, void* v)
{
	m_conns[k] = v;
	emit connOpen(k);
}

void TransferSkt::unsetCookie(const QString& k)
{
	m_conns.remove(k);
	emit connClose(k);
}

void* TransferSkt::getCookie(const QString& k)
{
	return m_conns.value(k, 0);
}

void TransferSkt::send(const QString& key, bool s2d, const QString& data)
{
	void* cookie = getCookie(key);
	if (!cookie) return;

	QByteArray bin;
	if (!TK::ascii2bin(data, bin, m_error))
		return;

	send(cookie, s2d, bin);
}

void TransferSkt::dump(const char* buf, qint32 len, TransferSkt::DIR dir, const QString& key)
{
	QString msg;
	QTextStream out(&msg);

	out << name() << " " << key << " ";

	switch (dir)
	{
	case TS2D: out << "S->D"; break;
	case TD2S: out << "D->S"; break;
	case SS2D: out << "S->D"; break;
	case SD2S: out << "D->S"; break;
	}

	out << " <" << len << "> " << TK::bin2ascii(buf, len);

	emit message(msg);
	emit dumpbin(msg, buf, len);
}

void TransferSkt::show(const QString& msg)
{
	emit message(msg);
}

void TransferSkt::setError(const QString& err)
{
	m_error = err;
}

TransferSktTcp::TransferSktTcp(QObject *parent)
: TransferSkt(parent)
{
}

TransferSktTcp::~TransferSktTcp()
{
	m_server.disconnect(this);
}

bool TransferSktTcp::open()
{
	if (m_server.listen(srcAddr(), srcPort()))
	{
		connect(&m_server, &QTcpServer::newConnection, this, &TransferSktTcp::newConnection);

		return true;
	}
	else
	{
		setError(QString("%1, %2").arg(m_server.serverError()).arg(m_server.errorString()));
	}

	return false;
}

bool TransferSktTcp::close(void* cookie)
{
	Conn* conn = (Conn*)cookie;
	
	if (conn->src)
		conn->src->disconnect(this);

	if (conn->dst)
		conn->dst->disconnect(this);

	delete conn->src;
	delete conn->dst;
	delete conn;

	return true;
}

void TransferSktTcp::close(QObject* obj)
{
	QMutexLocker locker(&m_door);

	Conn* conn = (Conn*)obj->property(PROP_CONN).value<void*>();
	if (!conn) return;

	if (conn->src)
	{
		conn->src->disconnect(this);

		if (obj == conn->dst)
			conn->src->deleteLater();
	}

	if (conn->dst)
	{
		conn->dst->disconnect(this);

		if (obj == conn->src)
			conn->dst->deleteLater();
	}

	unsetCookie(conn->key);
	delete conn;
}

void TransferSktTcp::error(QAbstractSocket::SocketError socketError)
{
	QTcpSocket* s = qobject_cast<QTcpSocket*>(sender());

	show(QString("TCP socket error %1, %2").arg(socketError).arg(s->errorString()));

	s->deleteLater();
}

void TransferSktTcp::close()
{
	m_server.close();
	m_server.disconnect(this);
}

void TransferSktTcp::newConnection()
{
	QTcpServer* svr = qobject_cast<QTcpServer*>(sender());
	if (!svr) return;

	QTcpSocket* src = svr->nextPendingConnection();
	while (src)
	{
		Conn* conn = new Conn;
		if (!conn)
		{
			src->deleteLater();
		}
		else
		{
			QTcpSocket* dst = new QTcpSocket();
			if (!dst)
			{
				delete conn;
				src->deleteLater();
			}
			else
			{
				src->setProperty(PROP_CONN, QVariant::fromValue((void*)conn));
				dst->setProperty(PROP_CONN, QVariant::fromValue((void*)conn));

				conn->src = src;
				conn->dst = dst;
				conn->key = TK::ipstr(src->peerAddress(),src->peerPort());

				connect(src, &QTcpSocket::readyRead, this, &TransferSktTcp::newData);
				connect(src, &QTcpSocket::destroyed, this, [this](QObject* obj) { close(obj); });
				connect(src, &QTcpSocket::disconnected, src, &QTcpSocket::deleteLater);
				connect(src, &QTcpSocket::errorOccurred, this, &TransferSktTcp::error);

				connect(dst, &QTcpSocket::readyRead, this, &TransferSktTcp::newData);
				connect(dst, &QTcpSocket::destroyed, this, [this](QObject* obj) { close(obj); });
				connect(dst, &QTcpSocket::disconnected, dst, &QTcpSocket::deleteLater);
				connect(dst, &QTcpSocket::connected, this, &TransferSktTcp::asynConnection);
				connect(dst, &QTcpSocket::errorOccurred, this, &TransferSktTcp::error);

				dst->connectToHost(dstAddr(), dstPort());

				setCookie(conn->key, conn);
			}
		}
		src = svr->nextPendingConnection();
	}
}

void TransferSktTcp::asynConnection()
{
	QTcpSocket* s = qobject_cast<QTcpSocket*>(sender());
	if (!s) return;

	Conn* conn = (Conn*)s->property(PROP_CONN).value<void*>();
	if (!conn) return;

	show(QString("connection %1 to %2:%3 opened!")
		.arg(conn->key, s->peerName()).arg(s->peerPort()));
}

void TransferSktTcp::newData()
{
	QMutexLocker locker(&m_door);

	QTcpSocket* s = qobject_cast<QTcpSocket*>(sender());
	if (!s) return;

	Conn* conn = (Conn*)s->property(PROP_CONN).value<void*>();
	if (!conn) return;

	QTcpSocket* d = (s == conn->src) ? conn->dst : conn->src;

	qint64 bufLen = s->bytesAvailable();
	char* buf = TK::createBuffer(bufLen, MAXBUFFER);
	if (!buf) return;

	qint64 readLen, writeLen, ioLen;
	
	readLen = 0;
	ioLen = s->read(buf, bufLen);
	while (ioLen > 0)
	{
		readLen += ioLen;
		ioLen = s->read(buf+readLen, bufLen-readLen);
	}

	if (ioLen >= 0)
	{
		recordRecv(readLen);

		writeLen = 0;
		ioLen = d->write(buf, readLen);
		while (ioLen > 0)
		{
			writeLen += ioLen;
			ioLen = d->write(buf+writeLen, readLen-writeLen);
		}

		if (ioLen >= 0)
		{
			recordSend(writeLen);
			dump(buf, readLen, ((s==conn->src) ? TS2D:TD2S), conn->key);
		}
	}

	TK::releaseBuffer(buf);
}

void TransferSktTcp::send(void* cookie, bool s2d, const QByteArray& bin)
{
	Conn* conn = (Conn*)cookie;

	QTcpSocket*  d = s2d ? conn->dst : conn->src;
	QHostAddress a = s2d ? dstAddr() : conn->src->peerAddress();
	quint16      p = s2d ? dstPort() : conn->src->peerPort();
	
	const char *  src = bin.constData(); 
	qint64 srcLen = bin.length();

	qint64 writeLen = 0;
	qint64 ioLen = d->write(src, srcLen);

	while (ioLen > 0)
	{
		writeLen += ioLen;
		ioLen = d->write(src+writeLen, srcLen-writeLen);
	}

	if (ioLen >= 0)
	{
		recordSend(writeLen);
		dump(src, srcLen, (s2d ? TS2D : TD2S), conn->key);
	}
}

TransferSktUdp::TransferSktUdp(QObject *parent)
: TransferSkt(parent)
{
}

TransferSktUdp::~TransferSktUdp()
{
	m_server.disconnect(this);
}

bool TransferSktUdp::open()
{
	if (m_server.bind(srcAddr(), srcPort(), QUdpSocket::ShareAddress))
	{
		connect(&m_server, &QUdpSocket::readyRead, this, &TransferSktUdp::newData);
		connect(&m_server, &QUdpSocket::errorOccurred, this, &TransferSktUdp::error);

		m_timer.setInterval(1000);
		connect(&m_timer, &QTimer::timeout, this, &TransferSktUdp::check);
		m_timer.start();

		return true;
	}
	else
	{
		setError(QString("%1, %2").arg(m_server.error()).arg(m_server.errorString()));
	}

	return false;
}

bool TransferSktUdp::close(void* cookie)
{
	Conn* conn = (Conn*)cookie;
	delete conn;
	return true;
}

void TransferSktUdp::close(QObject* obj)
{
	Conn* conn = (Conn*)obj->property(PROP_CONN).value<void*>();
	if (!conn) return;

	unsetCookie(conn->key);
	delete conn;
}

void TransferSktUdp::close()
{
	m_timer.stop();
	m_timer.disconnect(this);
	m_server.close();
	m_server.disconnect(this);
}

void TransferSktUdp::check()
{
	QDateTime now = QDateTime::currentDateTime();
	QList<Conn*> toRemove;

	// 检查超时的连接
	OBJMAP::const_iterator i;
	for (i = m_conns.constBegin(); i != m_conns.constEnd(); ++i)
	{
		Conn* conn = (Conn*)i.value();
		if (conn->stamp.secsTo(now) > 30)
		{
			toRemove << conn;
		}
	}

	foreach (Conn* conn, toRemove)
	{
		unsetCookie(conn->key);
		delete conn;
	}
}

void TransferSktUdp::error(QAbstractSocket::SocketError socketError)
{
	QUdpSocket* s = qobject_cast<QUdpSocket*>(sender());

	show(QString("UDP socket error %1, %2").arg(socketError).arg(s->errorString()));
}

void TransferSktUdp::newData()
{
	QMutexLocker locker(&m_door);

	QUdpSocket* s = qobject_cast<QUdpSocket*>(sender());
	if (!s) return;

	if (s != &m_server) return;

			qint64 bufferSize = MAXBUFFER;
		char* buf = TK::createBuffer(bufferSize, MAXBUFFER);
	if (!buf) return;

	qint64 readLen = 0;
	qint64 ioLen = s->readDatagram(buf, MAXBUFFER);
	while (ioLen > 0)
	{
		readLen += ioLen;
		ioLen = s->readDatagram(buf+readLen, MAXBUFFER-readLen);
	}

	if (readLen > 0)
	{
		recordRecv(readLen);

		QHostAddress addr;
		quint16 port;
		s->readDatagram(buf, readLen, &addr, &port);

		QString key = TK::ipstr(addr, port);
		Conn* conn = (Conn*)getCookie(key);

		if (!conn)
		{
			conn = new Conn;
			if (conn)
			{
				conn->dst = new QUdpSocket();
				if (conn->dst)
				{
					conn->addr = addr;
					conn->port = port;
					conn->key = key;
					conn->stamp = QDateTime::currentDateTime();

					connect(conn->dst, &QUdpSocket::readyRead, this, &TransferSktUdp::newData);
					connect(conn->dst, &QUdpSocket::destroyed, this, [this](QObject* obj) { close(obj); });
					connect(conn->dst, &QUdpSocket::errorOccurred, this, &TransferSktUdp::error);

					conn->dst->setProperty(PROP_CONN, QVariant::fromValue((void*)conn));

					setCookie(conn->key, conn);
				}
				else
				{
					delete conn;
					conn = 0;
				}
			}
		}
		else
		{
			conn->stamp = QDateTime::currentDateTime();
		}

		if (conn && conn->dst)
		{
			qint64 writeLen = 0;
			qint64 ioLen = conn->dst->writeDatagram(buf, readLen, dstAddr(), dstPort());
			while (ioLen > 0)
			{
				writeLen += ioLen;
				ioLen = conn->dst->writeDatagram(buf+writeLen, readLen-writeLen, dstAddr(), dstPort());
			}

			if (ioLen >= 0)
			{
				recordSend(writeLen);
				dump(buf, readLen, SS2D, conn->key);
			}
		}
	}

	TK::releaseBuffer(buf);
}

void TransferSktUdp::send(void* cookie, bool s2d, const QByteArray& bin)
{
	Conn* conn = (Conn*)cookie;

	QUdpSocket* d = s2d ? &m_server : conn->dst;
	QHostAddress a = s2d ? dstAddr() : conn->addr;
	quint16      p = s2d ? dstPort() : conn->port;
	
	const char *  src = bin.constData(); 
	qint64 srcLen = bin.length();

	qint64 writeLen = 0;
	qint64 ioLen = s2d ? d->writeDatagram(src, srcLen, a, p) : d->writeDatagram(src, srcLen, a, p);

	while (ioLen > 0)
	{
		writeLen += ioLen;
		ioLen = s2d ? d->writeDatagram(src+writeLen, srcLen-writeLen, a, p) : d->writeDatagram(src+writeLen, srcLen-writeLen, a, p);
	}

	if (ioLen >= 0)
	{
		recordSend(writeLen);
		dump(src, srcLen, (s2d ? SS2D : SD2S), conn->key);
	}
}

